/* hal_spi.c */
#include "hal_spi.h"

/* there are some features not listed here by specific implemanted by hardware.
 * SPI_CTRL_SPDEN - enable the sample point delay.
 * SPI_CTRL_CSDEN - enable to support the quad line spi.
 * SPI_CTRL_DHZEN - enable to support high z mode when the pcs is de-assert.
 * SPI_CTRL_PINCFG- setup the pin swap function.
 */
void SPI_InitMaster(SPI_Type * base, SPI_MasterInit_Type * init)
{
    base->CTRL = 0u; /* disable the spi engine before any settings. */
    
    uint32_t ctrl = SPI_CTRL_MODE_MASK; /* master mode. */

#if 0
    if (init->PcsActiveMode == SPI_PcsActiveMode_High)
    {
        ctrl = SPI_CTRL_CSPOL_MASK; /* all 4 pcs share the same settings in this software driver. */
    }
#endif

    if (init->EnableHighzOutputOnIdle)
    {
        ctrl |= SPI_CTRL_DHZEN_MASK;
    }
    
    base->CTRL = ctrl; /* disable the peripheral before setting clock. */

    SPI_ClkTimingConf_Type clk_timing_conf;
    clk_timing_conf.DivFromClkSrc = init->ClkSrcFreq / init->BaudrateBaseFreq; /* spi clock div. */
    if (clk_timing_conf.DivFromClkSrc >= 2u)
    {
        /* DIV SPI clock period is DIV + 2 divided SPI clock divided by TXCFG[PRESCALE] field. */
        clk_timing_conf.DivFromClkSrc -= 2u;
    }
    clk_timing_conf.DelayFromSckToPcs = 1u;
    clk_timing_conf.DelayFromPcsToSck = 1u;
    clk_timing_conf.DelayBetweenFrames = 4u;
    SPI_SetClkTimingConf(base, &clk_timing_conf);

    /* setup fifo watermark. */
    base->TXFIFO = SPI_TXFIFO_RESET_MASK | SPI_TXFIFO_WATER(init->TxFifoWatermark);
    base->RXFIFO = SPI_RXFIFO_RESET_MASK | SPI_RXFIFO_WATER(init->RxFifoWatermark);
    
    base->CTRL |= SPI_CTRL_EN_MASK; /* enable the peripheral. */
    
    /* always unlock the permission to access the high 8-bit of txcfg register. */
    base->TXCFG &= ~SPI_TXCFG_CONTC_MASK;
}

/* there are some features not listed here by specific implemanted by hardware.
 * SPI_TXCFG_CONTC - enable to insert new command during the current transfer.
 * SPI_TXCFG_MSKTX - enable to disable tx fifo when in half-duplex transfer.
 * SPI_TXCFG_WIDTH - enable to support 2-line or 4-line transfer mode.
 * 
 */
//void SPI_SetTxFrameConf(SPI_Type * base, SPI_TxFrameConf_Type * cmd)
void SPI_ActiveXferChannel(SPI_Type * base, uint32_t channel, SPI_XferChannelConf_Type * conf)
{
    /* ctrl. */
    if (conf->PcsActiveMode == SPI_PcsActiveMode_High)
    {
        base->CTRL |= 1u << (SPI_CTRL_CSPOL_SHIFT + channel);
    }
    else /* SPI_PcsActiveMode_Low. */
    {
        base->CTRL &= ~(1u << (SPI_CTRL_CSPOL_SHIFT + channel));
    }
    
    //uint32_t txcfg = 0u;
    uint32_t txcfg = base->TXCFG & SPI_TXCFG_CONT_MASK; /* leave the cont field to other standalone api. */
    
    /* always unlock the permission to access the high 8-bit of txcfg register. */
    //base->TXCFG &= ~SPI_TXCFG_CONTC_MASK;

    /* write the txcfg as normal. */
    txcfg |= (conf->PolarityPhase << SPI_TXCFG_CPHA_SHIFT)
            | SPI_TXCFG_PRESCALE(conf->Prescale)
            | SPI_TXCFG_PCS(channel)
            | SPI_TXCFG_FRAMESZ(conf->FrameBits-1u)
            ;
    
    if (conf->DataBitOrder == SPI_DataBitOrder_LSB)
    {
        txcfg |= SPI_TXCFG_LSBF_MASK;
    }

    if (conf->EnableByteSwap)
    {
        txcfg |= SPI_TXCFG_BSW_MASK;
    }

    if (conf->XferMode == SPI_XferMode_RxOnly)
    {
        txcfg |= SPI_TXCFG_MSKTX_MASK;
    }
    else if (conf->XferMode == SPI_XferMode_TxOnly)
    {
        txcfg |= SPI_TXCFG_MSKRX_MASK;
    }
    
    //base->TXCFG = txcfg;
#if 0
    if (cmd->EnableKeepPcs)
    {
        txcfg |= SPI_TXCFG_CONT_MASK; // | SPI_TXCFG_CONTC_MASK;
    }
#endif
    base->TXCFG = txcfg;
}

void SPI_RequestKeepPcs(SPI_Type * base, bool enable)
{
    if (enable)
    {
        base->TXCFG |= SPI_TXCFG_CONT_MASK;
    }
    else
    {
        base->TXCFG &= ~SPI_TXCFG_CONT_MASK;
    }
}

void SPI_Deinit(SPI_Type * base)
{
    base->CTRL = 0u; /* disable the spi peripheral. */
}

void SPI_SetClkTimingConf(SPI_Type * base, SPI_ClkTimingConf_Type * conf)
{
    base->CLK = SPI_CLK_DIV(conf->DivFromClkSrc) /* DIV SPI clock period is DIV + 2 divided SPI clock divided by TXCFG[PRESCALE] field. */
              | SPI_CLK_FMDLY(conf->DelayBetweenFrames)
              | SPI_CLK_PCSSCK(conf->DelayFromPcsToSck)
              | SPI_CLK_SCKPCS(conf->DelayFromSckToPcs)
              ;
}

/* TX DATA request DMA enable when TX fifo less than watermark or buff empty. */
void SPI_EnableTxDma(SPI_Type * base, bool enable)
{
    if (enable)
    {
        base->CTRL |= SPI_CTRL_TXDMAEN_MASK;
    }
    else
    {
        base->CTRL &= ~SPI_CTRL_TXDMAEN_MASK;
    }
}

/* RX DATA request DMA enable when RX fifo great than watermark or buff full. */
void SPI_EnableRxDma(SPI_Type * base, bool enable)
{
    if (enable)
    {
        base->CTRL |= SPI_CTRL_RXDMAEN_MASK;
    }
    else
    {
        base->CTRL &= ~SPI_CTRL_RXDMAEN_MASK;
    }
}

void SPI_EnableInterrupts(SPI_Type * base, uint32_t flags)
{
    base->INTE |= flags;
}

void SPI_DisableInterrupts(SPI_Type * base, uint32_t flags)
{
    base->INTE &= ~flags;
}

uint32_t SPI_GetStatusFlags(SPI_Type * base)
{
    return base->STS;
}

void SPI_ClearStatusFlags(SPI_Type * base, uint32_t flags)
{
    base->STS = flags;
}

void SPI_SetRxMatchConf(SPI_Type * base, SPI_RxMatchConf_Type * conf)
{
    if (conf->Mode == SPI_MatchMode_Disabled)
    {
        base->CTRL &= ~(SPI_CTRL_MATCFG_MASK | SPI_CTRL_RXMO_MASK); /* disable match mode and clear match mode. */
    }
    else
    {
        base->CTRL = (base->CTRL & ~SPI_CTRL_MATCFG_MASK) | SPI_CTRL_MATCFG(conf->Mode);
        base->CTRL |= SPI_CTRL_RXMO_MASK; /* only store data after match. */
        base->MATCH0 = conf->Match0;
        base->MATCH1 = conf->Match1;
    }
}

void SPI_PutFifoDataIn(SPI_Type * base, uint32_t dat)
{
    base->DATA = dat;
}

uint32_t SPI_GetFifoDataOut(SPI_Type * base)
{
    return base->DATA;
}

void SPI_InitSlave(SPI_Type * base, SPI_SlaveInit_Type * init)
{
    base->CTRL = 0u; /* disable the spi engine before any settings. */

    uint32_t ctrl = 0u; /* slave mode. */

    /* spi slave use the pcs signal directly, or regenerate the internal pcs signal when txcfg[cpha]=1. */
    if (init->EnableAutoPcs)
    {
        ctrl |= SPI_CTRL_AUTOPCS_MASK;
    }
    
    if (init->EnableHighzOutputOnIdle)
    {
        ctrl |= SPI_CTRL_DHZEN_MASK;
    }
    
    base->CTRL = ctrl;
    
    /* setup fifo watermark. */
    base->TXFIFO = SPI_TXFIFO_RESET_MASK | SPI_TXFIFO_WATER(init->TxFifoWatermark);
    base->RXFIFO = SPI_RXFIFO_RESET_MASK | SPI_RXFIFO_WATER(init->RxFifoWatermark);
    
    base->CTRL |= SPI_CTRL_EN_MASK; /* enable the peripheral. */
}

void SPI_ResetTxFifo(SPI_Type * base)
{
    base->TXFIFO |= SPI_TXFIFO_RESET_MASK;
}

/* EOF. */

